/**
 * MiniGolf Scorecard
 * Copyright (C) 2010  MiriTheQuiet, http://minigolfscorecard.googlecode.com/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package cz.mtq.mgsc.jsr75;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;

import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.io.file.FileSystemRegistry;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.List;

import cz.mtq.mgsc.MgscMidlet;
import cz.mtq.mgsc.Settings;
import cz.mtq.mgsc.command.AbstractCommand;
import cz.mtq.mgsc.export.csv.PlayersToCsvExport;

/**
 * 
 * @author MiriTheQuiet
 */
public class FileBrowser {

	private final List browser;

	private FileConnection fc;

	private Image folderImg;

	private AbstractCommand levelUp;

	private AbstractCommand export;

	private final MgscMidlet midlet;

	private static final int EXPORT_CMD_PRIORITY = 0;
	// assigned by the API
	// private static final int SELECT_CMD_PRIORITY = 0
	private static final int BACK_CMD_PRIORITY = 1;
	private static final int EXIT_CMD_PRIORITY = 2;

	private static final String URL_START = "file:///";

	private Directory currentDirectory;
	
	//cached instances of directory subtypes
	private final RootsDirectory roots = new RootsDirectory();
	private RootDirectory root;
	private SubDirectory subDir;

	private final Displayable previous;

	public FileBrowser(final MgscMidlet midlet, final Displayable previous) throws IOException {
		this.midlet = midlet;
		this.previous = previous;
		// init list with roots
		try {
			folderImg = Image.createImage(MgscMidlet.class.getResourceAsStream("folder.png"));
		} catch (IOException e) {
			// we just do not show the image...
		}
		// init browser
		browser = new List(Settings.getResource(Settings.TEXT_EXPORT_TO), List.IMPLICIT);
		browser.setCommandListener(midlet);
		// init FileBrowser
		init();
		// register command for directory browsing
		midlet.addCommand(null, new AbstractCommand(List.SELECT_COMMAND, midlet) {
			protected void exec() throws Exception {
				new Thread() {

					public void run() {
						Directory selectedDirectory;
						try {
							selectedDirectory = currentDirectory.getSelectedDirectory();
							selectedDirectory.listDirectories();
						} catch (Exception e) {
							handleFatalException(e);
							return;
						}
						if (selectedDirectory.isLevelUpCommandVisible()) {
							// adding the back button if its not present
							midlet.addCommand(FileBrowser.this.browser, getLevelUpCommand());
						} else {
							// remove the back button
							FileBrowser.this.midlet.removeCommand(FileBrowser.this.browser, getLevelUpCommand());
						}
						if (selectedDirectory.isExportCommandVisible()) {
							midlet.addCommand(FileBrowser.this.browser, getExportCommand());
						} else {
							FileBrowser.this.midlet.removeCommand(FileBrowser.this.browser, getExportCommand());
						}
						// currentDirectory.clearDirectoryCache();
						currentDirectory = selectedDirectory;
					}

				}.start();
			}
		});
		// add command for exiting browser
		midlet.addCommand(browser, new AbstractCommand(new Command(Settings.getResource(Settings.TEXT_EXIT), Command.EXIT, EXIT_CMD_PRIORITY),
				midlet) {

			protected void exec() throws Exception {
				try {
					FileBrowser.this.discardConnection();
				} catch (IOException e) {
					e.printStackTrace();
				}
				midlet.setCurrent(previous);
			}
		});
	}

	public void init() throws IOException {
		new Thread() {

			public void run() {
				browser.deleteAll();
				// current directory is fake = rootsDirectory
				currentDirectory = roots;
				// list its content
				try {
					currentDirectory.listDirectories();
				} catch (Exception e) {
					handleFatalException(e);
					return;
				}
			}

		}.start();
	}

	private void discardConnection() throws IOException {
		// close the connection and set it to null, not needed anymore
		if (fc != null) {
			try {
				fc.close();
			} finally {
				fc = null;
			}
		}
	}

	private boolean addBrowserItem(String folder, Image img, String nameOfItemToBeSelected) {
		boolean itemToBeSelectedFound = false;
		if (folder.endsWith("/")) {
			int index = browser.append(folder, img);
			if (folder.equals(nameOfItemToBeSelected)) {
				browser.setSelectedIndex(index, true);
				itemToBeSelectedFound = true;
			}
		}
		return itemToBeSelectedFound;
	}

	public List getBrowser() {
		return browser;
	}

	private String getCurrentFolder() {
		return browser.getString(browser.getSelectedIndex());
	}

	private AbstractCommand getLevelUpCommand() {
		if (levelUp == null) {
			levelUp = new AbstractCommand(new Command(Settings.getResource(Settings.TEXT_BACK), Command.EXIT, BACK_CMD_PRIORITY), null) {

				protected void exec() throws Exception {
					new Thread() {

						public void run() {
							try {
								currentDirectory.moveToParentDirectory();
								currentDirectory.parent.listDirectories();
							} catch (Exception e) {
								handleFatalException(e);
								return;
							}
							// currentDirectory.clearDirectoryCache();
							currentDirectory = currentDirectory.parent;
							if (currentDirectory.isLevelUpCommandVisible()) {
								// adding the back button if its not present
								midlet.addCommand(FileBrowser.this.browser, getLevelUpCommand());
							} else {
								// remove the back button
								FileBrowser.this.midlet.removeCommand(FileBrowser.this.browser, getLevelUpCommand());
							}
							if (currentDirectory.isExportCommandVisible()) {
								midlet.addCommand(FileBrowser.this.browser, getExportCommand());
							} else {
								FileBrowser.this.midlet.removeCommand(FileBrowser.this.browser, getExportCommand());
							}
						}

					}.start();

				}
			};
		}
		return levelUp;
	}

	private AbstractCommand getExportCommand() {
		if (export == null) {
			export = new AbstractCommand(new Command(Settings.getResource(Settings.TEXT_EXPORT), Command.EXIT, EXPORT_CMD_PRIORITY), midlet) {

				protected void exec() throws Exception {
					// file url
					String fileUrl = new StringBuffer(fc.getURL()).append(PlayersToCsvExport.createFileName()).toString();
					currentDirectory.saveFile(fileUrl);
				}
			};

		}
		return export;
	}

	private abstract class Directory {
		protected Directory parent;
		/**
		 * @see FileConnection#list() for details
		 */
		protected String name;
		/**
		 * @see FileConnection#list() for details
		 */
		protected String previousSubfolder;

		void listDirectories() throws IOException {
			browser.deleteAll();
			boolean itemToBeSelectedFound = _listDirectories();
			if (!itemToBeSelectedFound && browser.size() > 0) {
				browser.setSelectedIndex(0, true);
			}
		}

		// abstract void clearDirectoryCache();

		abstract boolean _listDirectories() throws IOException;

		abstract boolean isExportCommandVisible();

		abstract boolean isLevelUpCommandVisible();

		abstract void moveToParentDirectory() throws IOException;

		abstract Directory getSelectedDirectory() throws IOException;

		void saveFile(String fileUrl) {
			// create new WRITE connection
			FileConnection writeFc = null;
			try {
				writeFc = (FileConnection) Connector.open(fileUrl, Connector.READ_WRITE);
				if (writeFc.exists()) {
					writeFc.delete();
				}
				writeFc.create();
				OutputStream os = null;
				try {
					os = writeFc.openOutputStream();
					PlayersToCsvExport.export(midlet.getPlayers(), os);
					// show an alert with file path
					midlet.showModalAlertInfo(Settings.getResource(Settings.TEXT_GAME_EXPORTED_TO_PATH_INFO), writeFc.getPath() + writeFc.getName());
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					if (os != null) {
						os.close();
					}
				}
			} catch (Exception e) {
				midlet.showModalAlertError(e.getMessage());
				e.printStackTrace();
			} finally {
				if (writeFc != null) {
					try {
						writeFc.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	private class RootsDirectory extends Directory {

		private RootsDirectory() {
			super();
			parent = this;
		}

		Directory getSelectedDirectory() throws IOException {
			// reference sub folder with selected name
			if (root == null){
				root = new RootDirectory();
			}
			RootDirectory dir = root; 
			dir.parent = this;
			dir.name = getCurrentFolder();
			// create new connection
			fc = (FileConnection) Connector.open(URL_START + dir.name, Connector.READ);
			return dir;
		}

		boolean isExportCommandVisible() {
			return false;
		}

		boolean isLevelUpCommandVisible() {
			return false;
		}

		boolean _listDirectories() {
			Enumeration dirs = FileSystemRegistry.listRoots();
			boolean itemToBeSelectedFound = false;
			while (dirs.hasMoreElements()) {
				if (addBrowserItem((String) dirs.nextElement(), folderImg, previousSubfolder)) {
					itemToBeSelectedFound = true;
				}
			}
			return itemToBeSelectedFound;
		}

		void moveToParentDirectory() {
			throw new UnsupportedOperationException("Not supported");
		}
	}

	private class RootDirectory extends Directory {

		Directory getSelectedDirectory() throws IOException {
			if (subDir == null){
				subDir = new SubDirectory();
			}
			SubDirectory d = subDir;
			d.parent = this;
			d.name = getCurrentFolder();
			fc.setFileConnection(d.name);
			return d;
		}

		boolean isExportCommandVisible() {
			return true;
		}

		boolean isLevelUpCommandVisible() {
			return true;
		}

		boolean _listDirectories() throws IOException {
			// list available dirs
			return listDirectoriesAndPopulateBrowser(fc, previousSubfolder);
		}

		void moveToParentDirectory() throws IOException {
			parent.previousSubfolder = name;
		}
	}

	private class SubDirectory extends Directory {

		boolean _listDirectories() throws IOException {
			return listDirectoriesAndPopulateBrowser(fc, previousSubfolder);
		}

		Directory getSelectedDirectory() throws IOException {
			SubDirectory d = new SubDirectory();
			d.parent = this;
			d.name = getCurrentFolder();
			fc.setFileConnection(d.name);
			return d;
		}

		boolean isExportCommandVisible() {
			return true;
		}

		boolean isLevelUpCommandVisible() {
			return true;
		}

		void moveToParentDirectory() throws IOException {
			fc.setFileConnection("..");
			parent.previousSubfolder = name;
		}
	}

	private boolean listDirectoriesAndPopulateBrowser(FileConnection fc, String nameOfItemToBeSelected) throws IOException {
		Enumeration files = fc.list();
		boolean itemToBeSelectedFound = false;
		while (files.hasMoreElements()) {
			if (addBrowserItem((String) files.nextElement(), folderImg, nameOfItemToBeSelected)) {
				itemToBeSelectedFound = true;
			}
		}
		return itemToBeSelectedFound;
	}

	/**
	 * Discards the connection using {@link #discardConnection()}, prints stacktrace, shows modal alert using
	 * {@link MgscMidlet#showModalAlertError(String)} with nextDisplayable as {@link #previous}.
	 * 
	 * @param e
	 */
	private void handleFatalException(Exception e) {
		try {
			discardConnection();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		e.printStackTrace();
		midlet.showModalAlertError(e.getMessage(), previous);
	}
}
